﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Web;
using System.Linq;

namespace CSC.Google
{
    /// <summary>
    /// PageSpeed object used to request PageSpeedData from the google API
    /// </summary>
    public class PageSpeed
    {
        #region Private Variables
        private static List<string> supportedLanguages = new List<string>() { "ar", "bg", "ca", "zh-TW", "zh-CN", "hr", "cs", "da", "nl", "en", "en-GB", "fil-PH", "fi", "fr", "de", "el", /*"iw"*/"he", "hi", "hu", "id", "it", "ja", "ko", "lv", "lt", "no", "pl", "pt-BR", "pt-PT", "ro", "ru", "sr", "sk", "sl", "es", "sv", "th", "tr", "uk", "vi" };
        private string googlePageSpeedApiUrl = "https://www.googleapis.com/pagespeedonline/v1/runPagespeed";
        private string url = string.Empty;
        private Strategy strategy = Strategy.Desktop;
        private bool prettyPrint = true;
        private string apiKey = string.Empty;
        private string locale = "en";
        #endregion

        /// <summary>
        /// This Url can be used to go to the googlelabs webpage and start the speed test there
        /// </summary>
        public string DirectGoogleUrl { get { return "http://pagespeed.googlelabs.com/#url=" + HttpUtility.UrlEncode(url).Replace('%', '_') + "&amp;mobile=" + (strategy == Strategy.Mobile).ToString().ToLower(); } }

        /// <summary>
        /// <para>Url of the google page speed api. Default value is:</para>
        /// <seealso cref="https://www.googleapis.com/pagespeedonline/v1/runPagespeed" />
        /// </summary>
        public string GooglePageSpeedApiUrl { get { return googlePageSpeedApiUrl; } set { googlePageSpeedApiUrl = value; } }

        /// <summary>
        /// The URL of the page for which the Page Speed Online API should generate results.
        /// </summary>
        public string Url { get { return url; } set { url = value; } }

        /// <summary>
        /// The strategy to use when analyzing the page. Valid values are Desktop and Mobile.
        /// </summary>
        public Strategy Strategy { get { return strategy; } set { strategy = value; } }

        /// <summary>
        /// <para>Returns the response in a human-readable format if true.</para>
        /// <para>When this is false, it can reduce the response payload size, which might lead to better performance in some environments.</para>
        /// </summary>
        public bool PrettyPrint { get { return prettyPrint; } set { prettyPrint = value; } }

        /// <summary>
        /// <para>The locale that results should be generated in. See the list of supported locales:</para>
        /// <seealso cref="https://code.google.com/speed/page-speed/docs/languages.html" />
        /// <para>If the specified locale is not supported, the default locale is used (en).</para>
        /// </summary>
        public CultureInfo Locale { get { return new CultureInfo(locale.Replace("fil", "fil-PH").Replace("iw", "he").Replace('_', '-')); } set { locale = GetCultureFromString(value); } }

        /// <summary>
        /// Gets the locale string from a given CultureInfo object
        /// </summary>
        /// <param name="culture"></param>
        /// <returns></returns>
        private string GetCultureFromString(CultureInfo culture)
        {
            if (culture.LCID == CultureInfo.InvariantCulture.LCID) return "en";
            string lang = string.Empty;
            if (supportedLanguages.Contains(culture.Name)) lang = culture.Name;
            else if (supportedLanguages.Contains(culture.Parent.Name)) lang = culture.Parent.Name;
            if (string.IsNullOrEmpty(lang)) throw new ArgumentException("Language not supported: " + culture.Name);
            lang = lang.Replace("fil-PH", "fil");
            return lang.Replace("he", "iw").Replace('-', '_');
        }

        /// <summary>
        /// Creates a PageSpeed object
        /// </summary>
        /// <param name="apiKey">
        /// <para>Your API key identifies your project and provides you with API access, quota, and reports.</para>
        /// <para>Obtain your project's API key from the APIs console:</para>
        /// <seealso cref="https://code.google.com/apis/console" />
        /// </param>
        public PageSpeed(string apiKey)
        {
            this.apiKey = apiKey;
        }

        /// <summary>
        /// Returns a PageSpeedData object with all the parsed data from the API
        /// </summary>
        /// <returns>PageSpeedData object</returns>
        public PageSpeedData GetPageSpeedData()
        {
            WebResponse response = null;
            string pageSpeedUrl = googlePageSpeedApiUrl +
                "?url=" + HttpUtility.UrlEncode(url) +
                "&prettyprint=" + prettyPrint.ToString().ToLower() +
                "&strategy=" + strategy.ToString().ToLower() +
                "&locale=" + locale +
                "&key=" + apiKey;
            try
            {
                WebRequest wr = WebRequest.Create(pageSpeedUrl);
                wr.Method = "GET";
                response = wr.GetResponse();
                string content = string.Empty;
                PageSpeedData data = null;
                using (StreamReader sr = new StreamReader(response.GetResponseStream()))
                {
                    content = sr.ReadToEnd();
                    sr.Close();
                }
                using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(content)))
                {
                    DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(PageSpeedData));
                    data = jsonSerializer.ReadObject(ms) as PageSpeedData;
                    Type ruleResultsType = data.FormattedResults.RuleResults.GetType();
                    FieldInfo fi = ruleResultsType.GetField("content", BindingFlags.NonPublic | BindingFlags.Instance);
                    fi.SetValue(data.FormattedResults.RuleResults, content);
                    MethodInfo mi = ruleResultsType.GetMethod("ParseRuleResults", BindingFlags.NonPublic | BindingFlags.Instance);
                    mi.Invoke(data.FormattedResults.RuleResults, null);
                    ms.Close();
                }
                return data;
            }
            catch
            {
                return null;
            }

        }
    }
}
